using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using XNgine.Services;
using System;

namespace XNgine.CameraManagement.Cameras
{
    public class FirstPersonCamera : Camera
    {
        private readonly float PITCH_RANGE = MathHelper.ToRadians(89);
        private readonly float ROLL_RANGE = MathHelper.ToRadians(30);

        private Vector3? referenceTarget = null;
        private Vector3? referenceUp = null;
        private float time;
        private float yaw;
        private float pitch;
        private float roll;

        public FirstPersonCamera(string name) : base(name) { }

        public void Translate(Vector3 distance)
        {
            Position += distance;
            Target += distance;
        }
        public void Rotate(float yawAngle, float pitchAngle)
        {
            yaw += yawAngle;
            if (yaw >= MathHelper.Pi * 2 || yaw <= -MathHelper.Pi * 2)
                yaw = 0;

            pitch += pitchAngle;
            pitch = MathHelper.Clamp(pitch, -PITCH_RANGE, PITCH_RANGE);

            Quaternion targetRotation =
                Quaternion.CreateFromAxisAngle(Vector3.UnitY, -yaw) *
                Quaternion.CreateFromAxisAngle(Vector3.UnitX, pitch);

            Target = Position + Vector3.Transform(referenceTarget.Value, targetRotation);
        }
        public void Roll(float rollAngle)
        {
            roll += rollAngle;
            roll = MathHelper.Clamp(roll, -ROLL_RANGE, ROLL_RANGE);

            Quaternion rollTransform = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, roll);

            Up = Vector3.Normalize(Vector3.Transform(referenceUp.Value, rollTransform));
        }

        public override void Update()
        {
            if (referenceTarget == null)
                referenceTarget = Target;
            if (referenceUp == null)
                referenceUp = Up;

            time = (float)XEngine.Instance.GameTime.ElapsedGameTime.TotalSeconds;

            //UpdatePeeking();

            base.Update();
        }

        #region Peeking

        #region Peek Variables And Constants

        private readonly float PEEK_ROLL_SPEED = MathHelper.ToRadians(180);

        private bool peeking = false;
        private bool peekDirectionLeft = true;
        #endregion

        public void PeekLeft()
        {
            peeking = true;
            peekDirectionLeft = true;
        }
        public void PeekRight()
        {
            peeking = true;
            peekDirectionLeft = false;
        }
        public void StopPeeking()
        {
            peeking = false;
        }

        private void UpdatePeeking()
        {
            ApplyRoll();
        }
        private void ApplyRoll()
        {
            // Roll
            float rollAngle = time * PEEK_ROLL_SPEED;
            rollAngle = peeking && peekDirectionLeft ? -rollAngle : rollAngle;
            rollAngle = !peeking && !peekDirectionLeft ? -rollAngle : rollAngle;

            if (peeking && Math.Abs(roll) == ROLL_RANGE) return;
            if (!peeking && roll == 0) return;

            Roll(rollAngle);

            float min = peekDirectionLeft ? -ROLL_RANGE : 0;
            float max = peekDirectionLeft ? 0 : ROLL_RANGE;
            roll = MathHelper.Clamp(roll, min, max);
        }

        #endregion
    }
}